package org.codetranslators.compiler.tiger.semantics;

import java.util.Stack;

import org.codetranslators.common.Label;
import org.codetranslators.common.Temp;
import org.codetranslators.compiler.abstractassembly.*;
import org.codetranslators.compiler.mips.translate.*;
import org.codetranslators.compiler.tiger.abssyntree.OpExp;

import org.codetranslators.compiler.tiger.Debug;

/**
 *
 * 
 * @author shreesha123
 *
 */
public class TranslatingMediator 
{	
	public AssemType translateWhileExp(AssemType whileTestAssemType, AssemType whileBodyAssemType,
									   Label testLabel, Label endwhileLabel)
	{
		AssemType assemCond = null;
		Label doLabel = new Label();
		
		AssemExp assemExp = (AssemExp) whileTestAssemType;
		assemCond = assemExp.translateToCJump(doLabel, endwhileLabel);
	
		AssemType lblWhileTest  = new AssemName(testLabel);
		AssemType lblWhileEndTest  = translateLabel(endwhileLabel);
		
		Stack seqAssemTypes = new Stack();
		seqAssemTypes.push(translateLabel(testLabel));
		seqAssemTypes.push(assemCond);
		seqAssemTypes.push(translateLabel(doLabel));
		seqAssemTypes.push(whileBodyAssemType);
		seqAssemTypes.push(translateJump(lblWhileTest));
		seqAssemTypes.push(lblWhileEndTest);
		
		AssemType assemSeq = translateSeqStatement(seqAssemTypes);
	
		return assemSeq;
	}
		
	public AssemType translateAssignStm(AssemType assemTypeLHS, AssemType assemTypeRHS)
	{
		AssemType retAssemType = null;
		
		AssemExp assemExpLHS =  (AssemExp) assemTypeLHS;
		AssemExp assemExpRHS =  (AssemExp) assemTypeRHS;

		// Coerce the RHS to an int
		//MipsAssemExp transExpRHS = (MipsAssemExp) assemExpRHS.translateToIntType();
		//retAssemType = translateAssign(assemExpLHS, transExpRHS);
		
		retAssemType = translateAssign(assemExpLHS, assemExpRHS);
		
		return retAssemType;
	}
	
	public AssemType translateBreakStm(Label lbl)
	{
		return translateJump(translateLabel(lbl));
	}
	
	public AssemType translateIfStm(AssemType assemTypeTest, AssemType assemTypeIf, AssemType assemTypeElse)
	{		
		AssemExp assemExpTest =  (AssemExp) assemTypeTest;
		
		// Create two labels - one for true for the if case and the other false for the else case
		Label ifLbl = new Label();
		Label elseLbl = new Label();
		Label endElseLbl = new Label();   // The "join" label

		AssemType assemCond =  assemExpTest.translateToCJump(ifLbl, elseLbl);
		AssemLabel assemJoinLabel = null;
		
		if(assemTypeIf.getType() == AssemType.VOID_TYPE)
		{
			// The if-then and else clauses are statements (eg - assign exps,while exps, etc.)
			AssemStm assemThenStm =  (AssemStm) assemTypeIf;
			AssemStm assemElseStm =  (AssemStm) assemTypeElse;
			
			// The statement to jump to at the end of the else clause
			AssemName assemNameJoinLabel = (AssemName) translateName(endElseLbl);
			AssemJump assemJoinJump  = (AssemJump)translateJump(assemNameJoinLabel);   
			
			Stack seqAssemTypes = new Stack();
			seqAssemTypes.push(assemCond);    // The conditional jump  
			seqAssemTypes.push(translateLabel(ifLbl)); // The "if-true" label
			seqAssemTypes.push(assemThenStm);   // The statement to be executed if cond. is true
			if(assemElseStm != null)
			{
				seqAssemTypes.push(assemJoinJump); // end of the "else", since there is an else
				seqAssemTypes.push(translateLabel(elseLbl)); // The "else" label
				seqAssemTypes.push(assemElseStm);  // The statement to be executed if cond. is false
				assemJoinLabel = (AssemLabel) translateLabel(endElseLbl);
			}
			else
			{
				// The else label is the join label
				assemJoinLabel = (AssemLabel)translateLabel(elseLbl);
			}
			
			seqAssemTypes.push(assemJoinLabel); 
			
			return translateSeqStatement(seqAssemTypes);
		}
		else
		{
			// The if-then and else clauses are expressions yielding a value (eg - operator expressions, etc.)
			// Therefore, this should result in a MipsAssemSeqExp expression.
			
			Temp temp = new Temp();
			AssemTemp assemTemp = (AssemTemp) translateTemp(temp);
			AssemExp assemThenExp =  (AssemExp) assemTypeIf;
			AssemExp assemElseExp =  (AssemExp) assemTypeElse;
			
			// The statement to jump to at the end of the else clause
			AssemName assemNameJoinLabel = (AssemName) translateName(endElseLbl);
			AssemJump assemJoinJump  = (AssemJump)translateJump(assemNameJoinLabel);   
			
			Stack seqAssemTypes = new Stack();
			seqAssemTypes.push(assemCond);    // The conditional jump  
			seqAssemTypes.push(translateLabel(ifLbl)); // The "if-true" label
			if(assemThenExp.getDescription().equals("StmSeqAndExp"))
			{
				// The expression is a SeqExp; push the statement first and get the expression
				seqAssemTypes.push(((AssemSeqExp)assemThenExp).getStm());
				assemThenExp = ((AssemSeqExp)assemThenExp).getExp();
				if(assemElseExp == null) assemTemp = (AssemTemp) assemThenExp.translateToIntType();
			}
				
			if(assemElseExp != null)
			{
				// Create a new move statement for moving the if value into a temp and push the statement
				AssemMove moveIfExpIntoTemp = new AssemMove(assemTemp, assemThenExp);
				seqAssemTypes.push(moveIfExpIntoTemp);   // The statement to be executed if cond. is true
				
				// Do the else part now
				seqAssemTypes.push(assemJoinJump); // end of the "else", since there is an else
				seqAssemTypes.push(translateLabel(elseLbl)); // The "else" label
				
				if(assemElseExp.getDescription().equals("StmSeqAndExp"))
				{
					// The expression is a SeqExp; push the statement first and get the expression
					seqAssemTypes.push(((AssemSeqExp)assemElseExp).getStm());
					assemElseExp = ((AssemSeqExp)assemElseExp).getExp();		
				}
				// Create a new statement that assigns the expression into the temp
				AssemMove moveElseExpIntoTemp = new AssemMove(assemTemp, assemElseExp);
				seqAssemTypes.push(moveElseExpIntoTemp);
				
				assemJoinLabel = (AssemLabel) translateLabel(endElseLbl);
			}
			else
			{
				// The else label is the join label
				assemJoinLabel = (AssemLabel)translateLabel(elseLbl);
			}
			
			seqAssemTypes.push(assemJoinLabel); 
			seqAssemTypes.push(assemTemp); 
			return translateSeqStatement(seqAssemTypes);

		}					
	}
	
	public AssemType translateIntLiteral(int val)
	{
		return translateConstant(val);
	}
	
	public AssemType translateOperatorExp(int operType, AssemType assemTypeLeft,
			AssemType assemTypeRight)
	{
		int assemOperType = getAssemOperatorType(operType);
		AssemBinOpExp opExp = (AssemBinOpExp) translateBinOp(assemOperType, assemTypeLeft, assemTypeRight);
		return opExp.translateToIntType();
	}
	
	public AssemType translateVarExp(int varNum, boolean isArgument, int wordSize,
									 int staticLevelNum)
	{
		Temp tempFP = Temp.getTemp("FP");
		if(!isArgument)
		{
			// This is a local variable in a function; it is either at a specific location from the
			// frame pointer OR from frame pointer of the given level
			AssemConst assemTypeRight = (AssemConst)translateConstant(varNum * wordSize);
			AssemType assemTypeLeft = null;
			if(staticLevelNum == 0)
				assemTypeLeft = (AssemTemp)translateTemp(tempFP);
			else
			{
				AssemTemp levelTemp = (AssemTemp) translateTemp(tempFP);
				AssemConst assemLevelRight = (AssemConst)translateConstant(staticLevelNum);
				AssemBinOpExp levelExp = (AssemBinOpExp)translateBinOp(
						AssemBinOpExp.PLUS, levelTemp, assemLevelRight);
				assemTypeLeft = levelExp.translateToIntType();
			}
			
			AssemBinOpExp assemOffSet = (AssemBinOpExp)translateBinOp(
										AssemBinOpExp.MINUS, assemTypeLeft, assemTypeRight);
			return new AssemMemory(assemOffSet);
		}
		else
		{
			// This is an argument to the function; the first four will be in $a0 - $a3; the rest will
			// on the stack BEFORE the frame pointer as outgoing arguments in the caller's stack
			AssemExp retExp = null;
			Temp paramTemp = null;
			if(varNum == 1)
			{
				paramTemp = Temp.getTemp("a0");
				retExp = (AssemTemp) translateTemp(paramTemp);
			}
			else if(varNum == 2)
			{
				paramTemp = Temp.getTemp("a1");
				retExp = (AssemTemp) translateTemp(paramTemp);
			}
			else if(varNum == 3)
			{
				paramTemp = Temp.getTemp("a2");
				retExp = (AssemTemp) translateTemp(paramTemp);
			}
			else if(varNum == 4)
			{
				paramTemp = Temp.getTemp("a3");
				retExp = (AssemTemp) translateTemp(paramTemp);
			}
			else
			{
				// The number of arguments to this function is greater than four; this argument will be in the
				// outgoing parameter space of the caller
				
			}
			return retExp;
		}
	}
	
	public AssemType translateVarExp(Temp regTemp)
	{
		return translateTemp(regTemp);
	}
	
	public AssemType translateVarDec(int offSetFromFP, int wordSize, AssemType assemInitExpr)
	{
		Temp tempFP = new Temp();
		AssemConst assemTypeRight = (AssemConst)translateConstant(offSetFromFP * wordSize);		
		AssemTemp assemTypeLeft = (AssemTemp) translateTemp(tempFP);
		
		AssemBinOpExp assemOffSet = (AssemBinOpExp)translateBinOp(
			AssemBinOpExp.MINUS, assemTypeLeft, assemTypeRight);
		
		AssemMemory newVarMem = new AssemMemory(assemOffSet);
		
		// Create a move statement between the above and the init expression
		AssemMove assemMove = (AssemMove)translateMove(newVarMem, assemInitExpr);
		
		return assemMove;
	}
	
	public AssemType translateVarDec(Temp newVarTemp, AssemType assemInitExpr)
	{	
		// This is not a record
		AssemTemp assemTypeLeft = (AssemTemp) translateTemp(newVarTemp);
		
		// Create a move statement between the above and the init expression
		AssemMove assemMove = (AssemMove)translateMove(assemTypeLeft, assemInitExpr);
		
		return assemMove;
	}
	
	public AssemType translateFieldVarExp(AssemType assemTypeBase, int fieldOffSet)
	{
		AssemBinOpExp assemBin = new AssemBinOpExp(AssemBinOpExp.PLUS, (AssemExp)assemTypeBase,
										new AssemConst(ActivationFrame.WORDSIZE * fieldOffSet));
		return new AssemMemory(assemBin);
	}
	
	public AssemType translateRecordExp(int fieldCount,  Stack initExprStack)
	{
		Stack recordExpStack = new Stack();		
		int spaceRequired = fieldCount * ActivationFrame.WORDSIZE;
		
		Label label = new Label("malloc");
		Stack argsStack = new Stack();
		argsStack.push(new AssemConst(spaceRequired));
		AssemCallExp assemCallMalloc = (AssemCallExp) translateCallExp(label, argsStack, 1, 
				true, 0,  ActivationFrame.WORDSIZE);
		
		Temp resultTemp = new Temp();
		AssemTemp assemBaseTemp = (AssemTemp)translateTemp(resultTemp);
		AssemMove assemMoveResult = (AssemMove) translateMove(assemBaseTemp, assemCallMalloc);
		recordExpStack.push(assemMoveResult);
		while(!initExprStack.isEmpty())
		{
			AssemConst assemOffSetVal = (AssemConst)translateConstant(ActivationFrame.WORDSIZE * (--fieldCount));
			AssemBinOpExp assemOffSet = new AssemBinOpExp(AssemBinOpExp.PLUS, 
					assemBaseTemp, assemOffSetVal);
			AssemMemory assemMem = new AssemMemory(assemOffSet);
			AssemMove assemMoveFieldVal = (AssemMove)
					translateMove(assemMem, (AssemType)initExprStack.pop());
			recordExpStack.push(assemMoveFieldVal);
		}
		
		recordExpStack.push(assemBaseTemp);
		return translateSeqStatement(recordExpStack);
		
	}
	
	public AssemType translateCallExp(Label fLabel, Stack argsStack, int paramCount, 
			boolean hasReturnValue, int maxOutgoingParams, int wordSize)
	{		
		AssemName assemFuncName = (AssemName)translateName(fLabel);
		AssemExpList expList = null;
		while(!argsStack.isEmpty())
		{
			AssemExp exp = (AssemExp)argsStack.pop();
			exp = (AssemExp)exp.translateToIntType(); // must be converted to an int type
			expList = new AssemExpList(exp, expList);
		}
		return new AssemCallExp(assemFuncName, expList, hasReturnValue);
		
	}
	
	private int getAssemOperatorType(int operType)
	{
		if(operType == OpExp.AND) return AssemBinOpExp.AND;
		else if (operType == OpExp.DIV) return AssemBinOpExp.DIV;
		else if (operType == OpExp.EQ) return AssemBinOpExp.EQ;
		else if (operType == OpExp.GE) return AssemBinOpExp.GTE;
		else if (operType == OpExp.GT) return AssemBinOpExp.GT;
		else if (operType == OpExp.LE) return AssemBinOpExp.LTE;
		else if (operType == OpExp.LT) return AssemBinOpExp.LT;
		else if (operType == OpExp.MINUS) return AssemBinOpExp.MINUS;
		else if (operType == OpExp.MUL) return AssemBinOpExp.MUL;
		else if (operType == OpExp.NE) return AssemBinOpExp.NE;
		else if (operType == OpExp.OR) return AssemBinOpExp.OR;
		else if (operType == OpExp.PLUS) return AssemBinOpExp.PLUS;
		else return -1;
		
	}
	
	public AssemType translateSeqStatement(Stack seq)
	{
		AssemType retAssemType = null;
		AssemExp retExp = null;
		int count = 0;
		while(!seq.isEmpty())
		{
			AssemType temp = (AssemType)seq.pop();
			if(count == 0)
			{
				// This is the last statement or expression; check if it is an expression;
				// if it is, store it to return as a SeqExp
				if(temp instanceof AssemExp)
					retExp = (AssemExp) temp;
				else
					retAssemType  = new AssemSeq((AssemStm)temp, (AssemStm)retAssemType);
			}
			else
			{
				// If there is an expression in this sequence, coerce it to a statement,
				// since we are building a list of statements 	
				if(temp instanceof AssemExp)
				{
					AssemExp exp = (AssemExp) temp;
					temp = exp.translateToStatement();
				}
				retAssemType  = new AssemSeq((AssemStm)temp, (AssemStm)retAssemType);
			}
			count++;
		}
		
		if(retExp == null) return retAssemType;
		//if(retAssemType == null) return retExp;
		return new AssemSeqExp((AssemStm)retAssemType, retExp);
	}
	
	public AssemType translateReturnStm(AssemType assemBody)
	{
		if(assemBody instanceof AssemStm) return assemBody;
		
		AssemExp assemExp = (AssemExp) assemBody;
		
		Temp returnValueTemp = Temp.getTemp("v0");
		return new AssemMove((AssemExp)translateTemp(returnValueTemp), assemExp);
	}
	
	public AssemType translateAssign(AssemType assemTypeLHS, AssemType assemTypeRHS)
	{
		return new AssemMove((AssemExp)assemTypeLHS, 
						(AssemExp)assemTypeRHS);
	}
	
	public AssemType translateConditional(int rel, AssemType l, AssemType r, 
			  Label t, Label f)
	{
		return new AssemCJump(rel, (AssemExp) l, (AssemExp) r, t, f);
	}
	
	public AssemType translateJump(AssemType e)
	{
		return new AssemJump((AssemExp)e);
	}
	
	public AssemType translateLabel(Label label)
	{
		return new AssemLabel(label);
	}

	public AssemType translateMove(AssemType dst, AssemType src)
	{
		return new AssemMove((AssemExp)dst, (AssemExp)src);
	}
	
	public AssemType translateTemp(Temp temp)
	{
		return new AssemTemp(temp);
	}
	
	public AssemType translateConstant(int i)
	{
		return new AssemConst(i);
	}
	
	public AssemType translateCJump(int op, AssemType left, AssemType right, 
									Label tr, Label fl)
	{
		return new AssemCJump(op, (AssemExp) left, (AssemExp)right, tr, fl);
	}
	
	public AssemType translateSeqExp(AssemType stm, AssemType exp)
	{
		return new AssemSeqExp((AssemStm)stm,(AssemExp) exp);
	}
	
	public AssemType translateBinOp(int operType, AssemType assemTypeLeft, AssemType assemTypeRight)
	{
		return new AssemBinOpExp(operType, (AssemExp) assemTypeLeft,
									(AssemExp)  assemTypeRight);
	}

	public AssemType translateName(Label label)
	{
		return new AssemName(label);
	}
}


